#ifndef _COMPILATION_INTRUCTION_H_
#define _COMPILATION_INTRUCTION_H_

#include <compilation/Forward.h>

#include <cassert>
#include <iostream>
#include <memory>
#include <functional>

#include <compilation/Table.h>
#include <compilation/instruction/Traits.h>


namespace compilation
{

class VirtualInstruction
{
public:

  typedef InstructionType Type;
  typedef WorkListReusability Reusability;

private:

  Type m_type;
  VirtualInstruction* m_next;
  VirtualInstruction* m_previous;
  int m_registrationIndex;
  int m_dominationHeight;
  Scope const* m_scope;

public:

  mutable void* mark;

  template <typename _Type>
  _Type const& getMarkAs(void) const
  { return *reinterpret_cast<_Type const*>(mark); }

  template <typename _Type>
  _Type& getMarkAs(void)
  { return *reinterpret_cast<_Type*>(mark); }

  void setScope(Scope const& scope)
  { m_scope = &scope; }

  Scope const* getScope(void) const
  { return m_scope; }

protected:

  void setType(Type type)
  {
    assert(m_type == TInstructionUndefined);
    m_type = type;
  }

  void setNextTo(VirtualInstruction& next)
  { m_next = &next; }

  void setPreviousTo(VirtualInstruction& previous)
  { m_previous = &previous; }

  friend class IfInstruction;

  void setRegistrationIndex(int index)
  {
    assert((m_registrationIndex < 0) && (index >= 0));
    m_registrationIndex = index;
  }

public:

  VirtualInstruction(void)
    : m_type(TInstructionUndefined)
    , m_next(nullptr)
    , m_previous(nullptr)
    , m_registrationIndex(-1)
    , m_dominationHeight(0)
    , mark(nullptr) {}

  virtual ~VirtualInstruction(void) {}

  void setDominationHeight(int height)
  { m_dominationHeight = height; }

  int getDominationHeight(void) const
  { return m_dominationHeight; }

  template <typename _Type>
  bool hasType(void) const
  { return instruction_trait<_Type>::id == m_type; }

  template <typename _Type>
  void checkType(void) const
  {
    assert(dynamic_cast<_Type const*>(this));
    assert(hasType<_Type>());
  }

  Type type(void) const
  { return m_type; }

  virtual int countNexts(void) const
  { return m_next ? 1 : 0; }

  virtual int countPreviouses(void) const
  { return m_previous ? 1 : 0; }

  VirtualInstruction* getSNextInstruction(void) const
  { return m_next; }

  VirtualInstruction* getSPreviousInstruction(void) const
  { return m_previous; }

  void connectTo(VirtualInstruction& next)
  {
    assert(!m_next);
    assert(!next.m_previous);

    m_next = &next;
    next.m_previous = this;
  }

  VirtualInstruction* disconnectNext(void)
  {
    VirtualInstruction* result = m_next;
    if (m_next)
    {
      assert(m_next->m_previous == this);
      m_next->m_previous = nullptr;
      m_next = nullptr;
    };
    return result;
  }

  virtual void print(
      std::ostream& out,
      std::function<void(void)> const& indent
      = std::function<void(void)>()) const = 0;

  void clearMark(void)
  { mark = nullptr; }

  bool isValid(void) const
  { return m_registrationIndex >= 0; }

  int getRegistrationIndex(void) const
  { return m_registrationIndex; }

  friend class Function;
};

class ExpressionInstruction
    : public VirtualInstruction
{
private:

  std::auto_ptr<VirtualExpression> m_expression;

public:

  ExpressionInstruction(void)
  { setType(TExpression); }

  ExpressionInstruction& setExpression(VirtualExpression* expression)
  { m_expression.reset(expression); return *this; }

  VirtualExpression const* getExpression(void) const
  { return m_expression.get(); }

  VirtualExpression* getExpression(void)
  { return m_expression.get(); }

  template <ExpressionType _Type>
  void hasExpresionType() const
  { return m_expression && m_expression.hasType<_Type>(); }

  bool isPhi(void) const;

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const;
};

class LabelInstruction
    : public VirtualInstruction
{
public:

  typedef std::vector<GotoInstruction*> DominationFrontier;

private:

  GotoInstruction* m_goto;

  /// The dominator of the label.
  VirtualInstruction* m_dominator;

  DominationFrontier m_dominationFrontier;

public:

  LabelInstruction(void)
    : m_goto(nullptr), m_dominator(nullptr) { setType(TLabel); }

  VirtualInstruction* getSDominator(void) const
  { return m_dominator; }

  void setSDominator(VirtualInstruction* dominator)
  { m_dominator = dominator; }

  GotoInstruction* getGoto(void) const
  { return m_goto; }

  virtual int countPreviouses(void) const
  { return VirtualInstruction::countPreviouses() + (m_goto ? 1 : 0); }

  void setGotoFrom(GotoInstruction& gotoPoint)
  {
    assert(!m_goto);
    m_goto = &gotoPoint;
  }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const;

  void addDominationFrontier(GotoInstruction& gotoInstruction)
  { m_dominationFrontier.push_back(&gotoInstruction); }

  DominationFrontier& getDominationFrontier(void)
  { return m_dominationFrontier; }

  const DominationFrontier& getDominationFrontier(void) const
  { return m_dominationFrontier;}
};

class GotoInstruction
    : public VirtualInstruction
{
public:

  enum Context
  {
    CUndefined = 0,
    CAfterIfThen,
    CAfterIfElse,
    CLoop,
    CBeforeLabel
  };

  typedef std::vector<GotoInstruction*> DominationFrontier;

private:

  Context m_context;
  DominationFrontier m_dominationFrontier;

public:

  GotoInstruction(void)
    : m_context(CUndefined) { setType(TGoto); }

  bool isContext(Context ct) const
  { return (m_context == ct); }

  void setAfterIfThen(void)
  {
    assert(m_context == CUndefined);
    m_context = CAfterIfThen;
  }

  void setAfterIfElse(void)
  {
    assert(m_context == CUndefined);
    m_context = CAfterIfElse;
  }

  void setLoop(void)
  {
    assert(m_context == CUndefined);
    m_context = CLoop;
  }

  void setBeforeLabel(void)
  {
    assert(m_context == CUndefined);
    m_context = CBeforeLabel;
  }

  void connectToLabel(LabelInstruction& instruction)
  {
    this->setNextTo(instruction);
    instruction.setGotoFrom(*this);
  }

  void addDominationFrontier(GotoInstruction& gotoInstruction)
  { m_dominationFrontier.push_back(&gotoInstruction); }

  DominationFrontier& getDominationFrontier(void)
  { return m_dominationFrontier; }

  const DominationFrontier& getDominationFrontier(void) const
  { return m_dominationFrontier;}

  void setDominationFrontier(DominationFrontier const& frontier)
  { m_dominationFrontier = frontier; }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const;
};

class IfInstruction
    : public VirtualInstruction
{
private:

  std::auto_ptr<VirtualExpression> m_expression;
  GotoInstruction* m_then;

public:

  IfInstruction() { setType(TIf); }

  GotoInstruction const* getThen(void) const
  { return m_then; }

  GotoInstruction* getThen(void)
  { return m_then; }

  virtual int countNexts() const
  { return VirtualInstruction::countNexts() + (m_then ? 1 : 0); }

  IfInstruction& setExpression(VirtualExpression* expression)
  {
    m_expression.reset(expression);
    return *this;
  }

  VirtualExpression* getCondition(void)
  { return m_expression.get(); }

  void connectToThen(GotoInstruction& thenPoint)
  {
    m_then = &thenPoint;
    thenPoint.setPreviousTo(*this);
    thenPoint.setAfterIfThen();
  }

  void connectToElse(GotoInstruction& elsePoint)
  {
    connectTo(elsePoint);
    elsePoint.setAfterIfElse();
  }

  virtual void print(
      std::ostream &out,
      std::function<void(void)> const& indent) const;
};

class ReturnInstruction
    : public VirtualInstruction
{
private:

  std::auto_ptr<VirtualExpression> m_result;

public:

  ReturnInstruction(void)
  { setType(TReturn); }

  ReturnInstruction& setResult(VirtualExpression* expression)
  { m_result.reset(expression); return *this; }

  virtual void print(
      std::ostream &out,
      std::function<void(void)> const& indent) const;

  VirtualExpression* getResult(void)
  { return m_result.get(); }
};

class EnterBlockInstruction
    : public VirtualInstruction
{
private:

  Scope m_scope;

public:

  EnterBlockInstruction(void)
  { setType(TEnterBlock); }

  EnterBlockInstruction& setScope(const Scope& scope)
  {
    m_scope = scope;
    return *this;
  }

  const Scope& scope(void) const
  { return m_scope; }

  Scope& scope(void)
  { return m_scope; }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const;
};

class ExitBlockInstruction
    : public VirtualInstruction
{
private:

  int m_nbDeclarations;

public:

  ExitBlockInstruction(void)
    : m_nbDeclarations(0) { setType(TExitBlock); }

  ExitBlockInstruction& setScope(const Scope& sScope)
  {
    m_nbDeclarations = sScope.count();
    return *this;
  }

  virtual void print(std::ostream &out, std::function<void(void)> const& indent) const
  { out << "}\n"; }
};

} // compilation

#endif // _COMPILATION_INTRUCTION_H_
